home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / arm / util / textInput.py < prev    next >
Encoding:
Python Source  |  2012-05-18  |  6.6 KB  |  196 lines

  1. """
  2. Provides input validators that provide text input with various capabilities.
  3. These can be chained together with the first matching validator taking
  4. precidence.
  5. """
  6.  
  7. import os
  8. import curses
  9.  
  10. PASS = -1
  11.  
  12. class TextInputValidator:
  13.   """
  14.   Basic interface for validators. Implementations should override the handleKey
  15.   method.
  16.   """
  17.   
  18.   def __init__(self, nextValidator = None):
  19.     self.nextValidator = nextValidator
  20.   
  21.   def validate(self, key, textbox):
  22.     """
  23.     Processes the given key input for the textbox. This may modify the
  24.     textbox's content, cursor position, etc depending on the functionality
  25.     of the validator. This returns the key that the textbox should interpret,
  26.     PASS if this validator doesn't want to take any action.
  27.     
  28.     Arguments:
  29.       key     - key code input from the user
  30.       textbox - curses Textbox instance the input came from
  31.     """
  32.     
  33.     result = self.handleKey(key, textbox)
  34.     
  35.     if result != PASS:
  36.       return result
  37.     elif self.nextValidator:
  38.       return self.nextValidator.validate(key, textbox)
  39.     else: return key
  40.   
  41.   def handleKey(self, key, textbox):
  42.     """
  43.     Process the given keycode with this validator, returning the keycode for
  44.     the textbox to process, and PASS if this doesn't want to modify it.
  45.     
  46.     Arguments:
  47.       key     - key code input from the user
  48.       textbox - curses Textbox instance the input came from
  49.     """
  50.     
  51.     return PASS
  52.  
  53. class BasicValidator(TextInputValidator):
  54.   """
  55.   Interceptor for keystrokes given to a textbox, doing the following:
  56.   - quits by setting the input to curses.ascii.BEL when escape is pressed
  57.   - stops the cursor at the end of the box's content when pressing the right
  58.     arrow
  59.   - home and end keys move to the start/end of the line
  60.   """
  61.   
  62.   def handleKey(self, key, textbox):
  63.     y, x = textbox.win.getyx()
  64.     
  65.     if curses.ascii.isprint(key) and x < textbox.maxx:
  66.       # Shifts the existing text forward so input is an insert method rather
  67.       # than replacement. The curses.textpad accepts an insert mode flag but
  68.       # this has a couple issues...
  69.       # - The flag is only available for Python 2.6+, before that the
  70.       #   constructor only accepted a subwindow argument as per:
  71.       #   https://trac.torproject.org/projects/tor/ticket/2354
  72.       # - The textpad doesn't shift text that has text attributes. This is
  73.       #   because keycodes read by textbox.win.inch() includes formatting,
  74.       #   causing the curses.ascii.isprint() check it does to fail.
  75.       
  76.       currentInput = textbox.gather()
  77.       textbox.win.addstr(y, x + 1, currentInput[x:textbox.maxx - 1])
  78.       textbox.win.move(y, x) # reverts cursor movement during gather call
  79.     elif key == 27:
  80.       # curses.ascii.BEL is a character codes that causes textpad to terminate
  81.       return curses.ascii.BEL
  82.     elif key == curses.KEY_HOME:
  83.       textbox.win.move(y, 0)
  84.       return None
  85.     elif key in (curses.KEY_END, curses.KEY_RIGHT):
  86.       msgLen = len(textbox.gather())
  87.       textbox.win.move(y, x) # reverts cursor movement during gather call
  88.       
  89.       if key == curses.KEY_END and msgLen > 0 and x < msgLen - 1:
  90.         # if we're in the content then move to the end
  91.         textbox.win.move(y, msgLen - 1)
  92.         return None
  93.       elif key == curses.KEY_RIGHT and x >= msgLen - 1:
  94.         # don't move the cursor if there's no content after it
  95.         return None
  96.     elif key == 410:
  97.       # if we're resizing the display during text entry then cancel it
  98.       # (otherwise the input field is filled with nonprintable characters)
  99.       return curses.ascii.BEL
  100.     
  101.     return PASS
  102.  
  103. class HistoryValidator(TextInputValidator):
  104.   """
  105.   This intercepts the up and down arrow keys to scroll through a backlog of
  106.   previous commands.
  107.   """
  108.   
  109.   def __init__(self, commandBacklog = [], nextValidator = None):
  110.     TextInputValidator.__init__(self, nextValidator)
  111.     
  112.     # contents that can be scrolled back through, newest to oldest
  113.     self.commandBacklog = commandBacklog
  114.     
  115.     # selected item from the backlog, -1 if we're not on a backlog item
  116.     self.selectionIndex = -1
  117.     
  118.     # the fields input prior to selecting a backlog item
  119.     self.customInput = ""
  120.   
  121.   def handleKey(self, key, textbox):
  122.     if key in (curses.KEY_UP, curses.KEY_DOWN):
  123.       offset = 1 if key == curses.KEY_UP else -1
  124.       newSelection = self.selectionIndex + offset
  125.       
  126.       # constrains the new selection to valid bounds
  127.       newSelection = max(-1, newSelection)
  128.       newSelection = min(len(self.commandBacklog) - 1, newSelection)
  129.       
  130.       # skips if this is a no-op
  131.       if self.selectionIndex == newSelection:
  132.         return None
  133.       
  134.       # saves the previous input if we weren't on the backlog
  135.       if self.selectionIndex == -1:
  136.         self.customInput = textbox.gather().strip()
  137.       
  138.       if newSelection == -1: newInput = self.customInput
  139.       else: newInput = self.commandBacklog[newSelection]
  140.       
  141.       y, _ = textbox.win.getyx()
  142.       _, maxX = textbox.win.getmaxyx()
  143.       textbox.win.clear()
  144.       textbox.win.addstr(y, 0, newInput[:maxX - 1])
  145.       textbox.win.move(y, min(len(newInput), maxX - 1))
  146.       
  147.       self.selectionIndex = newSelection
  148.       return None
  149.     
  150.     return PASS
  151.  
  152. class TabCompleter(TextInputValidator):
  153.   """
  154.   Provides tab completion based on the current input, finishing if there's only
  155.   a single match. This expects a functor that accepts the current input and
  156.   provides matches.
  157.   """
  158.   
  159.   def __init__(self, completer, nextValidator = None):
  160.     TextInputValidator.__init__(self, nextValidator)
  161.     
  162.     # functor that accepts a string and gives a list of matches
  163.     self.completer = completer
  164.   
  165.   def handleKey(self, key, textbox):
  166.     # Matches against the tab key. The ord('\t') is nine, though strangely none
  167.     # of the curses.KEY_*TAB constants match this...
  168.     if key == 9:
  169.       currentContents = textbox.gather().strip()
  170.       matches = self.completer(currentContents)
  171.       newInput = None
  172.       
  173.       if len(matches) == 1:
  174.         # only a single match, fill it in
  175.         newInput = matches[0]
  176.       elif len(matches) > 1:
  177.         # looks for a common prefix we can complete
  178.         commonPrefix = os.path.commonprefix(matches) # weird that this comes from path...
  179.         
  180.         if commonPrefix != currentContents:
  181.           newInput = commonPrefix
  182.         
  183.         # TODO: somehow display matches... this is not gonna be fun
  184.       
  185.       if newInput:
  186.         y, _ = textbox.win.getyx()
  187.         _, maxX = textbox.win.getmaxyx()
  188.         textbox.win.clear()
  189.         textbox.win.addstr(y, 0, newInput[:maxX - 1])
  190.         textbox.win.move(y, min(len(newInput), maxX - 1))
  191.       
  192.       return None
  193.     
  194.     return PASS
  195.  
  196.